package com.gallsoft.amidar;

import java.util.Comparator;

import com.gallsoft.amidar.AnimationComponent.PlayerAnimations;
import com.gallsoft.amidar.CollisionParameters.HitType;

public class GameObjectFactory extends BaseObject {
    private final static int MAX_GAME_OBJECTS = 384;
    private final static ComponentPoolComparator sComponentPoolComparator = new ComponentPoolComparator();
    private FixedSizeArray<FixedSizeArray<BaseObject>> mStaticData;

    private FixedSizeArray<GameComponentPool> mComponentPools;
    private GameComponentPool mPoolSearchDummy;
    private GameObjectPool mGameObjectPool;

    
    // A list of game objects that can be spawned at runtime.
    public enum GameObjectType {
        INVALID(-1),        
        PLAYER (0),
        DOT(1),
        
        // End
        OBJECT_COUNT(-1);
        
        private final int mIndex;
        GameObjectType(int index) {
            this.mIndex = index;
        }
        
        public int index() {
            return mIndex;
        }
        
        // TODO: Is there any better way to do this?
        public static GameObjectType indexToType(int index) {
            final GameObjectType[] valuesArray = values();
            GameObjectType foundType = INVALID;
            
            for (int x = 0; x < valuesArray.length; x++) {
                GameObjectType type = valuesArray[x];
                
                if (type.mIndex == index) {
                    foundType = type;
                    break;
                }
            }
            
            return foundType;
        }
    }
    
    public GameObjectFactory() {
        super();
        
        mGameObjectPool = new GameObjectPool(MAX_GAME_OBJECTS);
        
        final int objectTypeCount = GameObjectType.OBJECT_COUNT.ordinal();
        mStaticData = new FixedSizeArray<FixedSizeArray<BaseObject>>(objectTypeCount);
        
        for (int x = 0; x < objectTypeCount; x++) {
            mStaticData.add(null);
        }


        // Define which game components (and how many) are required for the game
        ComponentClass[] componentTypes = {
        		new ComponentClass(AnimationComponent.class, 1),
        		new ComponentClass(MovementComponent.class, 1),
        		new ComponentClass(PlayerComponent.class, 1),
        		new ComponentClass(RenderComponent.class, 1),
        		new ComponentClass(SimplePhysicsComponent.class, 1),
        		new ComponentClass(SpriteComponent.class, 1)
        };

        mComponentPools = new FixedSizeArray<GameComponentPool>(componentTypes.length, sComponentPoolComparator);
        
        for (int x = 0; x < componentTypes.length; x++) {
            ComponentClass component = componentTypes[x];
            mComponentPools.add(new GameComponentPool(component.type, component.poolSize));
        }

        mPoolSearchDummy = new GameComponentPool(Object.class, 1);
    }
    
    
    @Override
	public void reset() {
		
	}

    private void addStaticData(GameObjectType type, GameObject object, SpriteComponent sprite) {
        FixedSizeArray<BaseObject> staticData = getStaticData(type);
        assert staticData != null;

        if (staticData != null) {
            final int staticDataCount = staticData.getCount();
            
            for (int x = 0; x < staticDataCount; x++) {
                BaseObject entry = staticData.get(x);
                if (entry instanceof GameComponent && object != null) {
                    object.add((GameComponent)entry);
                } else if (entry instanceof SpriteAnimation && sprite != null) {
                    sprite.addAnimation((SpriteAnimation)entry);
                }
            }
        }
    }
    
    public void clearStaticData() {
        final int typeCount = mStaticData.getCount();
        
        for (int x = 0; x < typeCount; x++) {
            FixedSizeArray<BaseObject> staticData = mStaticData.get(x);
            
            if (staticData != null) {
                final int count = staticData.getCount();
                
                for (int y = 0; y < count; y++) {
                    BaseObject entry = staticData.get(y);
                    
                    if (entry != null) {
                        if (entry instanceof GameComponent) {
                            releaseComponent((GameComponent)entry);
                        } 
                    }
                }
                
                staticData.clear();
                mStaticData.set(x, null);
            }
        }
    }

    private FixedSizeArray<BaseObject> getStaticData(GameObjectType type) {
        return mStaticData.get(type.ordinal());
    }
    
    private void setStaticData(GameObjectType type, FixedSizeArray<BaseObject> data) {
        int index = type.ordinal();
        assert mStaticData.get(index) == null;
        
        final int staticDataCount = data.getCount();

        for (int x = 0; x < staticDataCount; x++) {
            BaseObject entry = data.get(x);
            
            if (entry instanceof GameComponent) {
                ((GameComponent) entry).shared = true;
            }
        }
        
        mStaticData.set(index, data);
    }

    protected GameComponentPool getComponentPool(Class<?> componentType) {
        GameComponentPool pool = null;
        mPoolSearchDummy.objectClass = componentType;
        final int index = mComponentPools.find(mPoolSearchDummy, false);
        if (index != -1) {
            pool = mComponentPools.get(index);
        }
        return pool;
    }

    protected GameComponent allocateComponent(Class<?> componentType) {
        GameComponentPool pool = getComponentPool(componentType);
        assert pool != null;
        GameComponent component = null;
        if (pool != null) {
            component = pool.allocate();
        }
        return component;
    }
    
    protected void releaseComponent(GameComponent component) {
        GameComponentPool pool = getComponentPool(component.getClass());
        assert pool != null;
        if (pool != null) {
            component.reset();
            component.shared = false;
            pool.release(component);
        }
    }

    public void destroy(GameObject object) {
        object.commitUpdates();
        final int componentCount = object.getCount();
        for (int x = 0; x < componentCount; x++) {
            GameComponent component = (GameComponent)object.get(x);
            if (!component.shared) {
                releaseComponent(component);
            }
        }
        object.removeAll();
        object.commitUpdates();
        mGameObjectPool.release(object);
    }

    public void spawnFromWorld() {
    	GameObjectManager manager = sSystemRegistry.gameObjectManager;

    	GameObject object = spawn(GameObjectType.PLAYER, 155, 132, false);           	
       	manager.add(object);
        manager.setPlayer(object);        
    }
    
    public GameObject spawn(GameObjectType type, float x, float y, boolean horzFlip) {
        GameObject newObject = null;
        
        switch(type) {
            case PLAYER:
                newObject = spawnPlayer(x, y);
                break;
                
            case DOT:
                newObject = spawnDot(x, y);
                break;
        }
        
        return newObject;
    }
    
    public GameObject spawnPlayer(float positionX, float positionY) {
        TextureLibrary textureLibrary = sSystemRegistry.shortTermTextureLibrary;
        
        GameObject object = mGameObjectPool.allocate();
        object.getPosition().set(positionX, positionY);
        object.width = 20;
        object.height = 20;
        
        FixedSizeArray<BaseObject> staticData = getStaticData(GameObjectType.PLAYER);
        
        if (staticData == null) {
        	final int staticObjectCount = 4;
        	staticData = new FixedSizeArray<BaseObject>(staticObjectCount);
        	GameComponent movement = allocateComponent(MovementComponent.class);
            SimplePhysicsComponent physics = (SimplePhysicsComponent)allocateComponent(SimplePhysicsComponent.class);

            // Animation Data
            FixedSizeArray<CollisionVolume> basicAttackVolume = new FixedSizeArray<CollisionVolume>(1);
            basicAttackVolume.add(new AABoxCollisionVolume(0, 0, 20, 8, HitType.HIT));

            
            SpriteAnimation moveHoriz = new SpriteAnimation(PlayerAnimations.MOVE_HORIZ.ordinal(), 4);
            
            moveHoriz.addFrame(new AnimationFrame(textureLibrary.allocateTexture(R.drawable.monkey_l1), Utils.framesToTime(4, 1)));
            moveHoriz.addFrame(new AnimationFrame(textureLibrary.allocateTexture(R.drawable.monkey_l2), Utils.framesToTime(4, 1)));
            moveHoriz.addFrame(new AnimationFrame(textureLibrary.allocateTexture(R.drawable.monkey_l3), Utils.framesToTime(4, 1)));
            moveHoriz.addFrame(new AnimationFrame(textureLibrary.allocateTexture(R.drawable.monkey_l4), Utils.framesToTime(4, 1)));
            moveHoriz.setLoop(true);
            
            SpriteAnimation moveVert = new SpriteAnimation(PlayerAnimations.MOVE_VERT.ordinal(), 2);
            
            moveVert.addFrame(new AnimationFrame(textureLibrary.allocateTexture(R.drawable.monkey_u1), Utils.framesToTime(4, 1)));
            moveVert.addFrame(new AnimationFrame(textureLibrary.allocateTexture(R.drawable.monkey_u2), Utils.framesToTime(4, 1)));
            moveVert.setLoop(true);

            staticData.add(movement);
            staticData.add(physics);
            staticData.add(moveHoriz);
            staticData.add(moveVert);
            
            setStaticData(GameObjectType.PLAYER, staticData);
        }
        
        RenderComponent render = (RenderComponent)allocateComponent(RenderComponent.class);
        render.setPriority(SortConstants.PLAYER);
        render.setCameraRelative(false);
        
        PlayerComponent player = (PlayerComponent)allocateComponent(PlayerComponent.class);
        AnimationComponent animation = (AnimationComponent)allocateComponent(AnimationComponent.class);

        animation.setPlayer(player);

        SpriteComponent sprite = (SpriteComponent)allocateComponent(SpriteComponent.class);
        sprite.setSize((int)object.width, (int)object.height);
        sprite.setRenderComponent(render);
        animation.setSprite(sprite);

        object.life = PlayerComponent.MAX_PLAYER_LIFE;

        object.add(player);
        object.add(render);
        object.add(animation);
        object.add(sprite);  
        
        addStaticData(GameObjectType.PLAYER, object, sprite);

        sprite.playAnimation(PlayerAnimations.MOVE_HORIZ.ordinal());
        
        return object;
    }

    public GameObject spawnDot(float positionX, float positionY) {
        TextureLibrary textureLibrary = sSystemRegistry.shortTermTextureLibrary;
        
        GameObject object = mGameObjectPool.allocate();
        object.getPosition().set(positionX, positionY);
        object.width = 5;
        object.height = 5;
        
        FixedSizeArray<BaseObject> staticData = getStaticData(GameObjectType.DOT);
        
        if (staticData == null) {
            final int staticObjectCount = 2;
            staticData = new FixedSizeArray<BaseObject>(staticObjectCount);
            GameComponent movement = allocateComponent(MovementComponent.class);
            SimplePhysicsComponent physics = (SimplePhysicsComponent)allocateComponent(SimplePhysicsComponent.class);

            // Animation Data
            FixedSizeArray<CollisionVolume> basicVulnerabilityVolume = new FixedSizeArray<CollisionVolume>(1);
            basicVulnerabilityVolume.add(new AABoxCollisionVolume(0, 0, 5, 5, HitType.COLLECT));
            
            staticData.add(movement);
            staticData.add(physics);
            
            setStaticData(GameObjectType.DOT, staticData);
        }
        
        SpriteComponent sprite = (SpriteComponent)allocateComponent(SpriteComponent.class);
        sprite.setSize((int)object.width, (int)object.height);

        addStaticData(GameObjectType.DOT, object, sprite);

        return object;
    }


    // ******************** INNER CLASSES *******************************
    
    // TODO: I wish there was a way to do this automatically, but the ClassLoader doesn't seem
    // to provide access to the currently loaded class list.  There's some discussion of walking
    // the actual class file objects and using forName() to instantiate them, but that sounds
    // really heavy-weight.  For now I'll rely on (sucky) manual enumeration.
    class ComponentClass {
        public Class<?> type;
        public int poolSize;
        
        public ComponentClass(Class<?> classType, int size) {
            type = classType;
            poolSize = size;
        }
    }
    
    /** Comparator for game objects objects. */
    private final static class ComponentPoolComparator implements Comparator<GameComponentPool> {
        public int compare(final GameComponentPool object1, final GameComponentPool object2) {
            int result = 0;
            if (object1 == null && object2 != null) {
                result = 1;
            } else if (object1 != null && object2 == null) {
                result = -1;
            } else if (object1 != null && object2 != null) {
                result = object1.objectClass.hashCode() - object2.objectClass.hashCode();
            }
            return result;
        }
    }

    public class GameObjectPool extends TObjectPool<GameObject> {

        public GameObjectPool() {
            super();
        }
        
        public GameObjectPool(int size) {
            super(size);
        }
        
        @Override
        protected void fill() {
            for (int x = 0; x < getSize(); x++) {
                getAvailable().add(new GameObject());
            }
        }

        @Override
        public void release(Object entry) {
            ((GameObject)entry).reset();
            super.release(entry);
        }

    }

}
